home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Amiga Tools 2
/
Amiga Tools 2.iso
/
tools
/
jade
/
lisp
/
buffers.jl
< prev
next >
Wrap
Lisp/Scheme
|
1995-03-09
|
19KB
|
527 lines
;;;; buffers.jl -- High-level buffer/file handling
;;; Copyright (C) 1993, 1994 John Harper <jsh@ukc.ac.uk>
;;; This file is part of Jade.
;;; Jade is free software; you can redistribute it and/or modify it
;;; under the terms of the GNU General Public License as published by
;;; the Free Software Foundation; either version 2, or (at your option)
;;; any later version.
;;; Jade is distributed in the hope that it will be useful, but
;;; WITHOUT ANY WARRANTY; without even the implied warranty of
;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;;; GNU General Public License for more details.
;;; You should have received a copy of the GNU General Public License
;;; along with Jade; see the file COPYING. If not, write to
;;; the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
(defvar auto-save-p t
"When t files are auto-save'd regularly.")
(defvar default-auto-save-interval 120
"The number of seconds between each auto-save.")
(defvar make-backup-files t
"When non-nil backups of files are made when they are saved.")
(defvar backup-by-copying nil
"When non-nil all file backups are made by copying the file, not by
renaming it.")
(defvar else-backup-by-copying t
"Non-nil means make file backups by copying the file if it's not a good
idea to rename it. If `backup-by-copying' is non-nil this variable has no
effect.")
(defvar default-buffer (current-buffer)
"The `*jade*' buffer.")
;; Initialise the first window's buffer-list
(setq buffer-list (cons default-buffer nil))
(defvar standard-output default-buffer
"Stream that `prin?' writes its output to by default")
(defvar standard-input default-buffer
"Stream that `read' takes it's input from by default")
(defvar buffer-file-modtime 0
"Holds the modification time of the file this buffer was loaded from")
(make-variable-buffer-local 'buffer-file-modtime)
(defvar mildly-special-buffer nil
"When a buffer's `special' attribute is set kill-buffer will only kill
it totally if this variable is non-nil.")
(make-variable-buffer-local 'mildly-special-buffer)
(make-variable-buffer-local 'kill-buffer-hook)
(defvar enable-local-variables t
"Tells how to process local variable lists. t means process them
silently, nil means ignore them, anything else means to query each
variable being set.")
(defvar enable-local-eval 'maybe
"Tells how to process the `eval' local variable. Same options as
with `enable-local-variables'.")
(defvar local-variable-lines 20
"This variable defines how many of the bottom-most lines in a file are
searched for a `Local Variables:' section.")
(defun goto-buffer (buffer)
"Switch the current buffer to BUFFER which can either be a buffer-object
or a string naming an existing buffer. The selected buffer is moved to
the head of the buffer list. If BUFFER is a string and it doesn't name
an existing buffer a new one will be created with that name."
(interactive "BSwitch to buffer")
(when (stringp buffer)
(setq buffer (open-buffer buffer)))
(unless (bufferp buffer)
(signal 'bad-arg (list buffer 1)))
(setq buffer-list (cons buffer (delq buffer buffer-list)))
(set-current-buffer buffer))
(defun open-file (name)
"If no buffer containing file NAME exits try to create one.
After creating a new buffer (named after the file's (not path) name)
it first call the hook `read-file-hook' with arguments `(buffer-file-name
buffer)'.
If this hook returns nil (ie, no members of the hook decided to read the
file into memory) the file is read into the buffer verbatim.\n
Once the file is in memory, through the hook or otherwise, this function
then tries to initialise the correct editing mode for the file.\n
`open-file' always returns the buffer holding the file, or nil if it
doesn't exist."
(let
((buf (get-file-buffer name)))
(unless buf
(when (setq buf (make-buffer (file-name-nondirectory name)))
(add-buffer buf buffer-list)
(with-buffer buf
(unless (eval-hook 'read-file-hook name buf)
(set-buffer-file-name buf name)
(if (file-exists-p name)
(progn
(read-buffer name)
(setq buffer-file-modtime (file-modtime name)))
(message "New file")))
(fix-local-variables)
(set-buffer-modified buf nil)
(when auto-save-p
(setq auto-save-interval default-auto-save-interval))
(setq last-save-time (current-time)
buffer-undo-list nil)
(when (auto-save-file-newer-p name)
(message "Warning: Auto-saved file is newer")
(beep))
(when (and (file-exists-p name) (not (file-writable-p name)))
(set-buffer-read-only buf t))
(eval-hook 'open-file-hook buf)
(init-mode buf))))
buf))
;; Scans the end of a file for any local-variable definitions
(defun fix-local-variables ()
(unless enable-local-variables
(return))
(let
((pos (pos 0 (- (buffer-length) local-variable-lines))))
(when (< (pos-line pos) 0)
(set-pos-line pos 0))
(when (find-next-regexp "^(.*)Local Variables:(.*)$" pos)
(let
((re (concat ?^
(regexp-quote (copy-area (match-start 1) (match-end 1)))
"([^:]+):(.*)"
(regexp-quote (copy-area (match-start 2) (match-end 2)))
?$))
name value)
(setq pos (match-end))
(while (find-next-regexp re pos)
(setq pos (match-end)
name (copy-area (match-start 1) (match-end 1))
value (copy-area (match-start 2) (match-end 2)))
(cond
((and (equal name "End") (equal value ""))
(return))
((equal name "mode")
(when (or (eq enable-local-variables t)
(y-or-n-p (format nil "Use major mode %s?" value)))
(setq mode-name name)))
((equal name "eval")
(when (and enable-local-eval
(or (eq enable-local-eval t)
(y-or-n-p (format nil "Eval `%s'?" value))))
(eval (read-from-string value))))
(t
(when (or (eq enable-local-variables t)
(y-or-n-p (format nil "Set %s to %s?" name value)))
(setq name (intern name))
(make-local-variable name)
(set name (read-from-string value))))))))))
(defun find-file (name)
"Sets the current buffer to that containing the file NAME, if NAME
is unspecified it will be prompted for. If the file is not already in memory
`open-file' will be used to load it."
(interactive "FFind file: ")
(goto-buffer (open-file name)))
(defun find-file-read-only (name)
"Similar to `find-file' except that the buffer is edited in read-only mode."
(interactive "FFind file read-only:")
(let
((buf (open-file name)))
(when buf
(set-buffer-read-only buf t)
(goto-buffer buf))))
(defun find-alternate-file (name)
"If NAME is unspecified one will be prompted for. The current buffer is
killed and one editing NAME is found."
(interactive "FFind alternate file:")
(kill-buffer (current-buffer))
(goto-buffer (open-file name)))
(defun backup-file (file-name)
"If necessary make a backup of FILE-NAME. The file called FILE-NAME may or
may not exist after this function returns."
(when (and make-backup-files (file-regular-p name))
(let
((backup-name (concat name ?~)))
(if backup-by-copying
(copy-file name backup-name)
(if (and (file-owner-p name)
(= (file-nlinks name) 1))
(progn
(when (file-exists-p backup-name)
(delete-file backup-name))
(rename-file name backup-name))
(when else-backup-by-copying
(copy-file name backup-name)))))))
(defun write-file (buffer &optional name)
"Writes the contents of BUFFER to the file NAME, or to the one
that it is associated with."
(unless (stringp name)
(setq name (buffer-file-name buffer)))
(unless (eval-hook 'write-file-hook name buffer)
(let
((modes (when (file-exists-p name) (file-modes name))))
(backup-file name)
(when (write-buffer name buffer)
(when modes
(set-file-modes name modes))
t))))
(defun save-file (&optional buffer &aux name)
"Saves the buffer BUFFER, or the current buffer, to the file that it is
associated with, then sets the number of modifications made to this file
to zero.
Note: if no changes have been made to this buffer, it won't be saved."
(interactive)
(unless (bufferp buffer)
(setq buffer (current-buffer)))
(with-buffer buffer
(if (not (buffer-modified-p))
(message "No changes need to be saved!")
(let
((name (buffer-file-name)))
(when (and
(> (file-modtime name) buffer-file-modtime)
(not (yes-or-no-p "File on disk has changed since it was loaded, save anyway")))
(return nil))
(when (write-file buffer)
(set-buffer-modified buffer nil)
(setq last-save-time (current-time)
last-save-changes (buffer-changes)
last-user-save-changes (buffer-changes)
buffer-file-modtime (file-modtime name))
(delete-auto-save-file)
(message (concat "Wrote file `" name ?\') t))))))
(defun save-file-as (name &optional buffer)
"Saves the buffer BUFFER, or the current one, to the file NAME,
resetting the name of the buffer and the file that it is associated with
to reflect NAME. Also sets the modification count to zero."
(interactive "FWrite file:")
(unless (bufferp buffer)
(setq buffer (current-buffer)))
(with-buffer buffer
(set-buffer-file-name buffer name)
(set-buffer-name buffer (file-name-nondirectory name))
(when (write-file buffer)
(set-buffer-modified buffer nil)
(setq last-save-time (current-time)
last-save-changes (buffer-changes)
last-user-save-changes (buffer-changes)
buffer-file-modtime (file-modtime name))
(delete-auto-save-file)
(format t "Saved file `%s'." name))))
(defun insert-file (name &optional buffer)
"Inserts the file NAME into the buffer BUFFER (or the current one) before
the cursor position."
(interactive "FInsert file:")
(unless (bufferp buffer)
(setq buffer (current-buffer)))
(with-buffer buffer
(unless (eval-hook 'insert-file-hook name)
(insert (read-file name)))))
(defun open-buffer (name)
"If no buffer called NAME exists, creates one and adds it to the main
buffer-list. Always returns the buffer."
(let
((buf (get-buffer name)))
(unless buf
(when (setq buf (make-buffer name))
(add-buffer buf)))
buf))
(defun kill-buffer (buffer)
"Destroys BUFFER (can be an actual buffer or name of a buffer), first
checks whether or not we're allowed to with the function `check-changes'.
If it can be deleted, all windows displaying this buffer are switched
to the buffer at the head of the buffer-list, and BUFFER is removed
from the buffer-list (if it was in it)."
(interactive "bBuffer to kill:")
(cond
((bufferp buffer))
((stringp buffer)
(setq buffer (get-buffer buffer))))
(when (and buffer (check-changes buffer))
(eval-hook 'kill-buffer-hook buffer)
(unless (and (buffer-special-p buffer)
(null (with-buffer buffer mildly-special-buffer)))
(kill-mode buffer)
(destroy-buffer buffer))
(remove-buffer buffer)
t))
(defun bury-buffer (&optional buffer all-windows)
"Puts BUFFER (or the currently displayed buffer) at the end of the current
window's buffer-list then switch to the buffer at the head of the list.
If ALL-WINDOWS is non-nil this is done in all windows (the same buffer
will be buried in each window though)."
(interactive)
(unless buffer
(setq buffer (current-buffer)))
(let
((list (if all-windows
window-list
(cons (current-window) nil))))
(while list
(with-window (car list)
(let
((old-list (copy-sequence buffer-list)))
(setq buffer-list (nconc (delq buffer buffer-list)
(cons buffer nil)))
(set-current-buffer (car buffer-list))
;; It seems that buffer-list sometimes?
(when (/= (length buffer-list) (length old-list))
(error "buffer-list changed length!"))))
(setq list (cdr list)))))
(defun switch-to-buffer ()
"Prompt the user for the name of a buffer, then display it."
(interactive)
(let*
((default (or (nth 1 buffer-list) (current-buffer)))
(buffer (prompt-for-buffer (concat "Switch to buffer (default: "
(buffer-name default)
"):")
nil
default)))
(goto-buffer buffer)))
(defun rotate-buffers-forward ()
"Moves the buffer at the head of the buffer-list to be last in the list, the
new head of the buffer-list is displayed in the current window."
(interactive)
(let
((head (car buffer-list))
(end (nthcdr (1- (length buffer-list)) buffer-list)))
(rplacd end (cons head nil))
(setq buffer-list (cdr buffer-list))
(set-current-buffer (car buffer-list))))
;(defun rotate-buffers-backward (&aux end)
; "(rotate-buffers-backward)
;Moves the buffer at the end of the buffer-list to be first in the list, the
;new head of the buffer-list is displayed in the current window."
; (setq
; end (nthcdr (- 2 (length buffer-list)) buffer-list)
; buffer-list (cons (last buffer-list) buffer-list))
; (rplacd end nil)
; (set-current-buffer (car buffer-list)))
(defun check-changes (&optional buffer)
"Returns t if it is ok to kill BUFFER, or the current buffer. If unsaved
changes have been made to it the user is asked whether (s)he minds losing
them."
(or (not (buffer-modified-p buffer))
(yes-or-no-p (format nil "OK to lose change(s) to buffer `%s'"
(file-name-nondirectory (buffer-name buffer))))))
(defun goto-mark (mark)
"Switches (if necessary) to the buffer containing MARK at the position
of the mark. If the file containing MARK is not in memory then we
attempt to load it with `open-file'."
(when (markp mark)
(let
((file (mark-file mark))
(pos (mark-pos mark)))
(when (stringp file)
(setq file (open-file file)))
(set-auto-mark)
(goto-buffer file)
(goto-char pos))))
(defun set-auto-mark ()
"Sets the mark `auto-mark' to the current position (buffer & cursor-pos)."
(interactive)
(set-mark auto-mark (cursor-pos) (current-buffer))
(message "Set auto-mark."))
(defun swap-cursor-and-auto-mark ()
"Sets the `auto-mark' to the current position and then sets the current
position (buffer and cursor-pos) to the old value of `auto-mark'."
(interactive)
(let
((a-m-file (mark-file auto-mark))
(a-m-pos (copy-pos (mark-pos auto-mark))))
(set-auto-mark)
(when (stringp a-m-file)
(setq a-m-file (open-file a-m-file)))
(set-current-buffer a-m-file)
(goto-char a-m-pos)))
(defun split-line-indent ()
"Inserts a newline at the cursor position and then indents the new line
created to the indentation of the one above it."
(interactive)
(let
((old-indent-pos (next-line 1 (indent-pos))))
(split-line)
(if (empty-line-p)
(goto-glyph old-indent-pos)
(set-indent-pos old-indent-pos))))
(defun make-auto-save-name (name)
"Returns a string naming the file used to hold the auto-save'd file for
file NAME."
(concat (file-name-directory name) ?# (file-name-nondirectory name) ?#))
(defun auto-save-function (buffer)
"Automatically called when BUFFER is due to be automatically saved.
This function calls the hook `auto-save-hook', if this returns nil it then
saves it to the file specified by `make-auto-save-name' appiled to the
name of the file stored in BUFFER."
(format t "Auto-saving `%s'..." (buffer-name buffer))
(refresh-all)
(flush-output)
(with-buffer buffer
(if (or (eval-hook 'auto-save-hook buffer)
(write-buffer (make-auto-save-name (buffer-file-name))))
(format t "done.")
(error "Can't auto-save" buffer)
nil)))
(defun delete-auto-save-file (&optional buffer)
"Deletes the file used to store the auto-save'd copy of the file stored in
BUFFER, if such a file exists."
(interactive)
(let
((a-name (make-auto-save-name (buffer-file-name buffer))))
(when (file-exists-p a-name)
(delete-file a-name))))
(defun auto-save-file-newer-p (name)
"Returns t if there exists an automatically saved copy of file NAME which
is newer than NAME."
(let
((recover-name (make-auto-save-name name)))
(> (file-modtime recover-name) (file-modtime name))))
(defun recover-file (&optional buffer)
"Loads the auto-saved copy of the file stored in BUFFER into BUFFER
overwriting its current contents (if any changes are to be lost the user
will have to agree to this)."
(interactive)
(let
((recover-name (make-auto-save-name (buffer-file-name buffer))))
(unless buffer
(setq buffer (current-buffer)))
(when (and (file-exists-p recover-name) (check-changes buffer))
(with-buffer buffer
(read-buffer recover-name)
(set-buffer-modified buffer t)
(setq last-save-time (current-time))
(message (concat "Using " recover-name " as "
(buffer-file-name buffer)))))
buffer))
(defun revert-buffer (&optional buffer)
"Restores the contents of BUFFER (or current buffer) to the contents of the
file it was loaded from."
(interactive)
(unless buffer
(setq buffer (current-buffer)))
(if (and (auto-save-file-newer-p (buffer-file-name buffer))
(yes-or-no-p "auto-saved file is newer; recover-file instead?"))
(recover-file buffer)
(when (check-changes buffer)
(with-buffer buffer
(unless (eval-hook 'read-file-hook (buffer-file-name buffer) buffer)
(read-buffer (buffer-file-name buffer)))
(set-buffer-modified buffer nil)
(setq last-save-time (current-time))))))
(defun goto-line (line)
"Goto line number LINE. LINE counts from 1."
(interactive "NLine: ")
(set-auto-mark)
(goto-char (pos nil (1- line))))
(defun file-newer-than-file-p (file1 file2)
"Returns t of FILE1 was modified more recently than FILE2."
(> (file-modtime file1) (file-modtime file2)))
(defun save-some-buffers ()
"Asks whether or not to save any modified buffers, returns t if no modified
buffers are left."
(interactive)
(let
((bufs buffer-list)
buf
(unsaved-files-p nil))
(while (consp bufs)
(setq buf (car bufs))
(when (and (buffer-modified-p buf) (not (buffer-special-p buf)))
(if (y-or-n-p (concat "Save buffer " (buffer-name buf)))
(unless (save-file buf)
(setq unsaved-files-p t))
(setq unsaved-files-p t)))
(setq bufs (cdr bufs)))
(not unsaved-files-p)))
(defun save-and-quit ()
"Calls `save-some-buffers' and quits (after asking whether it's ok to lose
any unsaved buffers)."
(interactive)
(when (or (save-some-buffers)
(yes-or-no-p "Unsaved buffers exist; quit anyway?"))
(throw 'quit 0)))
(defun auto-save-mode (&optional disable)
"When this mode is enabled files are autosaved regularly if
they've been modified."
(interactive "P")
(if (or (/= auto-save-interval 0)
disable)
(progn
(setq auto-save-interval 0)
(message "Auto-save is now disabled in this buffer."))
(setq auto-save-interval default-auto-save-interval)
(message "Auto-save is now enabled for this buffer.")))